// Utility.cs
// 
// Copyright © 2009 FreeZzaph
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text.RegularExpressions;

namespace LibFreeZzaph
{
	
	/// <summary>
	/// Utility class provided to plug-in implementors for convenience. Contains
	/// methods that are useful in the course of a plug-in's life.
	/// </summary>
	public static class FreeZzaphUtility
	{
		
		/// <summary>
		/// Reads the contents of the stream into a byte array.
		/// data is returned as a byte array. An IOException is
		/// thrown if any of the underlying IO calls fail.
		/// </summary>
		/// <param name="stream">The stream to read.</param>
		/// <returns>A byte array containing the contents of the stream.</returns>
		private static byte[] ReadAllBytes(Stream source)
		{
		   
			try
			{
			   byte[] readBuffer = new byte[4096];

			   int totalBytesRead = 0;
			   int bytesRead;

			   while ((bytesRead = source.Read(readBuffer, totalBytesRead, readBuffer.Length - totalBytesRead)) > 0)
			   {
			       totalBytesRead += bytesRead;

			       if (totalBytesRead == readBuffer.Length)
			       {
			           int nextByte = source.ReadByte();
			           if (nextByte != -1)
			           {
			               byte[] temp = new byte[readBuffer.Length * 2];
			               Buffer.BlockCopy(readBuffer, 0, temp, 0, readBuffer.Length);
			               Buffer.SetByte(temp, totalBytesRead, (byte)nextByte);
			               readBuffer = temp;
			               totalBytesRead++;
			           }
			       }
			   }

			   byte[] buffer = readBuffer;
			   if (readBuffer.Length != totalBytesRead)
			   {
			       buffer = new byte[totalBytesRead];
			       Buffer.BlockCopy(readBuffer, 0, buffer, 0, totalBytesRead);
			   }
			   return buffer;
			}
			catch
			{
				return null;
			}
		}
		
		private static WebRequest PrepareWebRequest(Uri uri, string referer)
		{
			WebRequest request = WebRequest.Create(uri);
			if (request is HttpWebRequest)
			{
				HttpWebRequest httpRequest = (HttpWebRequest)request;
				httpRequest.UserAgent = "LibFreeZzaph/0.2";
				
				if (referer != null)
					httpRequest.Referer = referer;
			}
			
			return request;
		}
		
		/// <summary>
		/// Reads the contents of the URL into a string.
		/// </summary>
		/// <param name="uri">
		/// The URL to fetch the contents of
		/// </param>
		/// <param name="referer">
		/// The referring web page from which this file is downloaded. If there
		/// is no referring web page, set to null.
		/// </param>
		public static string FetchUrl(Uri uri, string referer)
		{
			WebRequest request = PrepareWebRequest(uri, referer);
			WebResponse response = request.GetResponse();
			Stream responseStream = response.GetResponseStream();
			TextReader reader = new StreamReader(responseStream);
			
			string responseData = reader.ReadToEnd();
			
			return responseData;
		}
		
		/// <summary>
		/// Reads the contents of the URL into a string.
		/// </summary>
		/// <param name="uri">
		/// The URL to fetch the contents of
		/// </param>
		public static string FetchUrl(Uri uri)
		{
			return FetchUrl(uri, null);
		}
		
		/// <summary>
		/// Reads the contents of the URL into a byte array.
		/// </summary>
		/// <param name="uri">
		/// The URL to fetch the contents of
		/// </param>
		/// <param name="referer">
		/// The referring web page from which this file is downloaded. If there
		/// is no referring web page, set to null.
		/// </param>
		public static byte[] FetchUrlBytes(Uri uri, string referer)
		{
			WebRequest request = PrepareWebRequest(uri, referer);
			WebResponse response = request.GetResponse();
			Stream responseStream = response.GetResponseStream();
			
			return FreeZzaphUtility.ReadAllBytes(responseStream);
		}
		
		/// <summary>
		/// Reads the contents of the URL into a byte array.
		/// </summary>
		/// <param name="uri">
		/// The URL to fetch the contents of
		/// </param>
		public static byte[] FetchUrlBytes(Uri uri)
		{
			return FetchUrlBytes(uri, null);
		}
		
		private static readonly Regex decimalEntity = new Regex("&#(\\d+);", RegexOptions.Compiled);
		private static readonly Regex sexEntity = new Regex("&#x([a-f0-9]+);", RegexOptions.IgnoreCase | RegexOptions.Compiled);
		private static readonly Regex namedEntity = new Regex("&(\\w+);", RegexOptions.IgnoreCase | RegexOptions.Compiled);
		private static readonly Dictionary<string, char> characterMap = new Dictionary<string, char>();
	
		// Initialize characterMap
		static FreeZzaphUtility() {
			characterMap.Add("nbsp", (char)160);
			characterMap.Add("iexcl", (char)161);
			characterMap.Add("cent", (char)162);
			characterMap.Add("pound", (char)163);
			characterMap.Add("curren", (char)164);
			characterMap.Add("yen", (char)165);
			characterMap.Add("brvbar", (char)166);
			characterMap.Add("sect", (char)167);
			characterMap.Add("uml", (char)168);
			characterMap.Add("copy", (char)169);
			characterMap.Add("ordf", (char)170);
			characterMap.Add("laquo", (char)171);
			characterMap.Add("not", (char)172);
			characterMap.Add("shy", (char)173);
			characterMap.Add("reg", (char)174);
			characterMap.Add("macr", (char)175);
			characterMap.Add("deg", (char)176);
			characterMap.Add("plusmn", (char)177);
			characterMap.Add("sup2", (char)178);
			characterMap.Add("sup3", (char)179);
			characterMap.Add("acute", (char)180);
			characterMap.Add("micro", (char)181);
			characterMap.Add("para", (char)182);
			characterMap.Add("middot", (char)183);
			characterMap.Add("cedil", (char)184);
			characterMap.Add("sup1", (char)185);
			characterMap.Add("ordm", (char)186);
			characterMap.Add("raquo", (char)187);
			characterMap.Add("frac14", (char)188);
			characterMap.Add("frac12", (char)189);
			characterMap.Add("frac34", (char)190);
			characterMap.Add("iquest", (char)191);
			characterMap.Add("Agrave", (char)192);
			characterMap.Add("Aacute", (char)193);
			characterMap.Add("Acirc", (char)194);
			characterMap.Add("Atilde", (char)195);
			characterMap.Add("Auml", (char)196);
			characterMap.Add("Aring", (char)197);
			characterMap.Add("AElig", (char)198);
			characterMap.Add("Ccedil", (char)199);
			characterMap.Add("Egrave", (char)200);
			characterMap.Add("Eacute", (char)201);
			characterMap.Add("Ecirc", (char)202);
			characterMap.Add("Euml", (char)203);
			characterMap.Add("Igrave", (char)204);
			characterMap.Add("Iacute", (char)205);
			characterMap.Add("Icirc", (char)206);
			characterMap.Add("Iuml", (char)207);
			characterMap.Add("ETH", (char)208);
			characterMap.Add("Ntilde", (char)209);
			characterMap.Add("Ograve", (char)210);
			characterMap.Add("Oacute", (char)211);
			characterMap.Add("Ocirc", (char)212);
			characterMap.Add("Otilde", (char)213);
			characterMap.Add("Ouml", (char)214);
			characterMap.Add("times", (char)215);
			characterMap.Add("Oslash", (char)216);
			characterMap.Add("Ugrave", (char)217);
			characterMap.Add("Uacute", (char)218);
			characterMap.Add("Ucirc", (char)219);
			characterMap.Add("Uuml", (char)220);
			characterMap.Add("Yacute", (char)221);
			characterMap.Add("THORN", (char)222);
			characterMap.Add("szlig", (char)223);
			characterMap.Add("agrave", (char)224);
			characterMap.Add("aacute", (char)225);
			characterMap.Add("acirc", (char)226);
			characterMap.Add("atilde", (char)227);
			characterMap.Add("auml", (char)228);
			characterMap.Add("aring", (char)229);
			characterMap.Add("aelig", (char)230);
			characterMap.Add("ccedil", (char)231);
			characterMap.Add("egrave", (char)232);
			characterMap.Add("eacute", (char)233);
			characterMap.Add("ecirc", (char)234);
			characterMap.Add("euml", (char)235);
			characterMap.Add("igrave", (char)236);
			characterMap.Add("iacute", (char)237);
			characterMap.Add("icirc", (char)238);
			characterMap.Add("iuml", (char)239);
			characterMap.Add("eth", (char)240);
			characterMap.Add("ntilde", (char)241);
			characterMap.Add("ograve", (char)242);
			characterMap.Add("oacute", (char)243);
			characterMap.Add("ocirc", (char)244);
			characterMap.Add("otilde", (char)245);
			characterMap.Add("ouml", (char)246);
			characterMap.Add("divide", (char)247);
			characterMap.Add("oslash", (char)248);
			characterMap.Add("ugrave", (char)249);
			characterMap.Add("uacute", (char)250);
			characterMap.Add("ucirc", (char)251);
			characterMap.Add("uuml", (char)252);
			characterMap.Add("yacute", (char)253);
			characterMap.Add("thorn", (char)254);
			characterMap.Add("yuml", (char)255);
			characterMap.Add("fnof", (char)402);
			characterMap.Add("Alpha", (char)913);
			characterMap.Add("Beta", (char)914);
			characterMap.Add("Gamma", (char)915);
			characterMap.Add("Delta", (char)916);
			characterMap.Add("Epsilon", (char)917);
			characterMap.Add("Zeta", (char)918);
			characterMap.Add("Eta", (char)919);
			characterMap.Add("Theta", (char)920);
			characterMap.Add("Iota", (char)921);
			characterMap.Add("Kappa", (char)922);
			characterMap.Add("Lambda", (char)923);
			characterMap.Add("Mu", (char)924);
			characterMap.Add("Nu", (char)925);
			characterMap.Add("Xi", (char)926);
			characterMap.Add("Omicron", (char)927);
			characterMap.Add("Pi", (char)928);
			characterMap.Add("Rho", (char)929);
			characterMap.Add("Sigma", (char)931);
			characterMap.Add("Tau", (char)932);
			characterMap.Add("Upsilon", (char)933);
			characterMap.Add("Phi", (char)934);
			characterMap.Add("Chi", (char)935);
			characterMap.Add("Psi", (char)936);
			characterMap.Add("Omega", (char)937);
			characterMap.Add("alpha", (char)945);
			characterMap.Add("beta", (char)946);
			characterMap.Add("gamma", (char)947);
			characterMap.Add("delta", (char)948);
			characterMap.Add("epsilon", (char)949);
			characterMap.Add("zeta", (char)950);
			characterMap.Add("eta", (char)951);
			characterMap.Add("theta", (char)952);
			characterMap.Add("iota", (char)953);
			characterMap.Add("kappa", (char)954);
			characterMap.Add("lambda", (char)955);
			characterMap.Add("mu", (char)956);
			characterMap.Add("nu", (char)957);
			characterMap.Add("xi", (char)958);
			characterMap.Add("omicron", (char)959);
			characterMap.Add("pi", (char)960);
			characterMap.Add("rho", (char)961);
			characterMap.Add("sigmaf", (char)962);
			characterMap.Add("sigma", (char)963);
			characterMap.Add("tau", (char)964);
			characterMap.Add("upsilon", (char)965);
			characterMap.Add("phi", (char)966);
			characterMap.Add("chi", (char)967);
			characterMap.Add("psi", (char)968);
			characterMap.Add("omega", (char)969);
			characterMap.Add("thetasym", (char)977);
			characterMap.Add("upsih", (char)978);
			characterMap.Add("piv", (char)982);
			characterMap.Add("bull", (char)8226);
			characterMap.Add("hellip", (char)8230);
			characterMap.Add("prime", (char)8242);
			characterMap.Add("Prime", (char)8243);
			characterMap.Add("oline", (char)8254);
			characterMap.Add("frasl", (char)8260);
			characterMap.Add("weierp", (char)8472);
			characterMap.Add("image", (char)8465);
			characterMap.Add("real", (char)8476);
			characterMap.Add("trade", (char)8482);
			characterMap.Add("alefsym", (char)8501);
			characterMap.Add("larr", (char)8592);
			characterMap.Add("uarr", (char)8593);
			characterMap.Add("rarr", (char)8594);
			characterMap.Add("darr", (char)8595);
			characterMap.Add("harr", (char)8596);
			characterMap.Add("crarr", (char)8629);
			characterMap.Add("lArr", (char)8656);
			characterMap.Add("uArr", (char)8657);
			characterMap.Add("rArr", (char)8658);
			characterMap.Add("dArr", (char)8659);
			characterMap.Add("hArr", (char)8660);
			characterMap.Add("forall", (char)8704);
			characterMap.Add("part", (char)8706);
			characterMap.Add("exist", (char)8707);
			characterMap.Add("empty", (char)8709);
			characterMap.Add("nabla", (char)8711);
			characterMap.Add("isin", (char)8712);
			characterMap.Add("notin", (char)8713);
			characterMap.Add("ni", (char)8715);
			characterMap.Add("prod", (char)8719);
			characterMap.Add("sum", (char)8721);
			characterMap.Add("minus", (char)8722);
			characterMap.Add("lowast", (char)8727);
			characterMap.Add("radic", (char)8730);
			characterMap.Add("prop", (char)8733);
			characterMap.Add("infin", (char)8734);
			characterMap.Add("ang", (char)8736);
			characterMap.Add("and", (char)8743);
			characterMap.Add("or", (char)8744);
			characterMap.Add("cap", (char)8745);
			characterMap.Add("cup", (char)8746);
			characterMap.Add("int", (char)8747);
			characterMap.Add("there4", (char)8756);
			characterMap.Add("sim", (char)8764);
			characterMap.Add("cong", (char)8773);
			characterMap.Add("asymp", (char)8776);
			characterMap.Add("ne", (char)8800);
			characterMap.Add("equiv", (char)8801);
			characterMap.Add("le", (char)8804);
			characterMap.Add("ge", (char)8805);
			characterMap.Add("sub", (char)8834);
			characterMap.Add("sup", (char)8835);
			characterMap.Add("nsub", (char)8836);
			characterMap.Add("sube", (char)8838);
			characterMap.Add("supe", (char)8839);
			characterMap.Add("oplus", (char)8853);
			characterMap.Add("otimes", (char)8855);
			characterMap.Add("perp", (char)8869);
			characterMap.Add("sdot", (char)8901);
			characterMap.Add("lceil", (char)8968);
			characterMap.Add("rceil", (char)8969);
			characterMap.Add("lfloor", (char)8970);
			characterMap.Add("rfloor", (char)8971);
			characterMap.Add("lang", (char)9001);
			characterMap.Add("rang", (char)9002);
			characterMap.Add("loz", (char)9674);
			characterMap.Add("spades", (char)9824);
			characterMap.Add("clubs", (char)9827);
			characterMap.Add("hearts", (char)9829);
			characterMap.Add("diams", (char)9830);
			characterMap.Add("quot", (char)34);
			characterMap.Add("amp", (char)38);
			characterMap.Add("lt", (char)60);
			characterMap.Add("gt", (char)62);
			characterMap.Add("OElig", (char)338);
			characterMap.Add("oelig", (char)339);
			characterMap.Add("Scaron", (char)352);
			characterMap.Add("scaron", (char)353);
			characterMap.Add("Yuml", (char)376);
			characterMap.Add("circ", (char)710);
			characterMap.Add("tilde", (char)732);
			characterMap.Add("ensp", (char)8194);
			characterMap.Add("emsp", (char)8195);
			characterMap.Add("thinsp", (char)8201);
			characterMap.Add("zwnj", (char)8204);
			characterMap.Add("zwj", (char)8205);
			characterMap.Add("lrm", (char)8206);
			characterMap.Add("rlm", (char)8207);
			characterMap.Add("ndash", (char)8211);
			characterMap.Add("mdash", (char)8212);
			characterMap.Add("lsquo", (char)8216);
			characterMap.Add("rsquo", (char)8217);
			characterMap.Add("sbquo", (char)8218);
			characterMap.Add("ldquo", (char)8220);
			characterMap.Add("rdquo", (char)8221);
			characterMap.Add("bdquo", (char)8222);
			characterMap.Add("dagger", (char)8224);
			characterMap.Add("Dagger", (char)8225);
			characterMap.Add("permil", (char)8240);
			characterMap.Add("lsaquo", (char)8249);
			characterMap.Add("rsaquo", (char)8250);
			characterMap.Add("euro", (char)8364);
		}

		/// <summary>
		/// Decodes HTML entities to their character equivalents. Handles both
		/// named and numerical entities. Any unrecognized named entities will
		/// be replaced with three question marks ("???")
		/// </summary>
		/// <param name="input">
		/// the string potentially containing HTML entities.
		/// </param>
		/// <returns>
		/// the same as the input string with the HTML entities replaced with
		/// their character equivalents.
		/// </returns>
		public static string DecodeHtmlEntities(string input) {
			String output = input;
			
			// First, decode named entities
			Match match = namedEntity.Match(output);
			while (match.Success) {
				string entity = match.Groups[1].ToString();
				
				try
				{
					char entityChar = characterMap[entity];
					output = output.Replace(match.Groups[0].ToString(), entityChar.ToString());
				}
				catch (KeyNotFoundException)
				{
					output = output.Replace(match.Groups[0].ToString(), "???");
				}
				match = match.NextMatch();
			}
			
			// Then decimal entities
			match = decimalEntity.Match(output);
			while (match.Success) {
				output = output.Replace(match.Groups[0].ToString(), ((char)int.Parse(match.Groups[1].ToString())).ToString());
				match = match.NextMatch();
			}
			
			// Then sexadecimal entities
			match = sexEntity.Match(output);
			while (match.Success) {
				output = output.Replace(match.Groups[0].ToString(), ((char)int.Parse(match.Groups[1].ToString(),System.Globalization.NumberStyles.HexNumber)).ToString());
				match = match.NextMatch();
			}
			
			return output;
			
		}
		
		/// <summary>
		/// Turns any string into a legal name string. Converts a string like
		/// "Fetish, Bizarre & Weird" into a more proper string, namely
		/// "fetish-bizarre-weird". This method does not provide any guarantees
		/// of uniqueness, and strings that don't contain any legal characters
		/// will return the empty string, or the string "-", depending on what
		/// the input is.
		/// </summary>
		/// <param name="value">
		/// The string to convert
		/// </param>
		/// <returns>
		/// The converted string
		/// </returns>
		public static string Prettify(string value) {
			// Remove illegal characters
			string prettified = Regex.Replace(value.ToLower(), "[^-a-z0-9]", "-");
			
			// Reduce hyphens to single
			while (prettified.Contains("--")) {
				prettified = prettified.Replace("--", "-");
			}
			
			// Sanitized string only contains alphanumerics and hyphens
			return prettified; 
		}
		
	}
}
